package com.hup.joystick.service.joyEvent;

import com.hup.joystick.constant.DeviceKey.KeyType;
import com.hup.joystick.constant.DeviceKey.StandardKeys;
import com.hup.joystick.constant.FunctionConstant.FunctionType;
import com.hup.joystick.constant.FunctionConstant.MouseMoveType;
import com.hup.joystick.model.FunctionConfig;
import com.hup.joystick.model.FunctionConfig.FunctionItem;
import com.hup.joystick.model.joystick.StandardEvent;
import com.hup.joystick.model.joystick.StandardEvent.StandardAction;
import com.hup.joystick.model.joystick.StandardKey;
import com.hup.utils.commons.hardware.HRobotJni;
import com.hup.utils.commons.hardware.ScreenUtil;
import lombok.extern.log4j.Log4j2;

import java.awt.Dimension;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Robot;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author hugan
 * @date 2020/10/22
 */
@Log4j2
public class MouseFunctionHandler implements IJoyEventHandler {

    private final MoveHandler moveHandler = new MoveHandler();
    private final ButtonHandler buttonHandler;

    public MouseFunctionHandler(Robot robot) {
        buttonHandler = new ButtonHandler(robot);
        moveHandler.start();
    }

    @Override
    public void releaseAllActiveFunction() {
        moveHandler.stopMove();
        buttonHandler.releaseAllActiveFunction();
    }

    @Override
    public void handleRelease(StandardEvent e, StandardKey standardKey) {
        /*
        moveHandler不单独处理抬起事件:
        .只要含有[按下事件]就解析 StandardEvent.currentPress
        .如果没有任何[按下事件],则releaseAllActiveFunction中就已经停止鼠标移动了
         */
        buttonHandler.handleRelease(e, standardKey);
    }

    @Override
    public void handlePress(ArrayList<StandardAction> newPress, ArrayList<StandardAction> currentPress,
            FunctionConfig matchConfig) {
        moveHandler.handleNewPress(newPress, currentPress, matchConfig);
        if (newPress.isEmpty()) return;//这句不加也可以,
        buttonHandler.handleNewPress(newPress, currentPress, matchConfig);
    }

    @Override
    protected void finalize() throws Throwable {
        moveHandler.shutDown();
        super.finalize();
    }

    /**
     * 鼠标移动逻辑类,移动速度:
     * .与轴的像素量无关
     * .与每秒移动帧数相关
     * .与[每秒移动1/n个轴,一个轴耗时n秒]相关
     * .移动的前1/n秒内,从微移开始加速
     * .模拟鼠标滚轮: {@link #pressedStick}
     */
    private static class MoveHandler extends Thread {
        /**
         * 鼠标每秒移动帧数
         */
        private static final int FPS = 60;
        /**
         * 每帧间隔ms
         */
        private static final int F_SPAN = 1000 / FPS;
        /**
         * 移动一个轴耗时n秒,每秒移动1/n个轴
         */
        private static final float TOTAL_SECOND = 1;
        /**
         * 每帧移动的(最大)像素量
         */
        private final int xPX, yPX;

        /**
         * 当前的移动功能 {@link FunctionType#MOUSE_MOVE},
         * 同一时间内,只能有一种移动类型:{@link KeyType#ARROW}, {@link KeyType#STICK}
         */
        private FunctionItem moveFunction;

        private final ReentrantLock lock = new ReentrantLock();
        private final Condition condition = lock.newCondition();
        /**
         * 实时的鼠标移动速度幅度/百分比[0,1]
         */
        private float xSpeed = 0, ySpeed = 0;
        /**
         * 是否按下摇杆;用于实现[(摇杆控制鼠标移动时)按下摇杆,模拟滚轮的功能];
         *
         * @apiNote 对于[模拟滚轮功能]的实现代码/变量名/方法名/逻辑,都是"混乱的",
         * 因为代码是后期在[移动鼠标功能]的基础上追加,目前不深究和大改旧代码
         */
        private boolean pressedStick;
        private boolean canRun;

        private MoveHandler() {
            super("MouseMoving");
            canRun = true;
            Dimension screenSize = ScreenUtil.getScreenSize();
            xPX = (int) (screenSize.width / TOTAL_SECOND / FPS);
            yPX = (int) (screenSize.height / TOTAL_SECOND / FPS);
            log.debug("xPX={}, yPX={}", xPX, yPX);
        }

        @Override
        public void run() {
            try {
                int frame = 0;//第n帧
                while (canRun) {
                    if (xSpeed == 0 && ySpeed == 0) {
                        //log.info("停止移动");
                        lock.lock();
                        try {
                            condition.await();
                        } finally {
                            lock.unlock();
                            frame = 0;
                        }
                        //log.info("开始移动");
                    }

                    if (!pressedStick) {
                        if (frame == 0) HRobotJni.showMouse();
                        //移动鼠标
                        int xDiff = (int) (xSpeed * xPX);
                        int yDiff = (int) (ySpeed * yPX);
                        if (frame < FPS / 3) {//前1/n秒内,从微移逐渐加速
                            frame++;
                            xDiff = (int) (xDiff * (frame * 3f / FPS));
                            yDiff = (int) (yDiff * (frame * 3f / FPS));
                        }
                        Point location = MouseInfo.getPointerInfo().getLocation();
                        int x = location.x + xDiff;
                        int y = location.y + yDiff;
                        //不检查[x,y]是否超出屏幕,支持多屏幕场景
                        boolean res = HRobotJni.mouseMove(x, y);
                        if (!res) log.warn("移动失败?");
                        //noinspection BusyWait
                        Thread.sleep(F_SPAN);
                    } else {
                        //模拟滚轮
                        if (frame % 6 == 0) {//n帧内触发一次滚轮事件
                            //log.info("模拟滚轮, {}, {}, {}", xSpeed, ySpeed, frame);
                            if (ySpeed != 0) HRobotJni.mouseWheel(ySpeed > 0 ? 1 : -1, false);
                            if (xSpeed != 0) HRobotJni.mouseWheel(xSpeed > 0 ? 1 : -1, true);
                        }
                        frame++;
                        Thread.sleep(F_SPAN);
                    }
                }
            } catch (InterruptedException e) {
                log.error("InterruptedException", e);
                Thread.currentThread().interrupt();
                canRun = false;
            } finally {
                canRun = false;
                log.info("ed");
            }
        }

        private void shutDown() {
            log.info("");
            canRun = false;
            stopMove();
        }

        private void stopMove() {
            moveFunction = null;
            pressedStick = false;
            this.xSpeed = 0;
            this.ySpeed = 0;
            //不用signalAll
        }

        /**
         * 处理鼠标移动功能; {@link #updateMoveState(ArrayList, ArrayList)}
         */
        private void handleNewPress(ArrayList<StandardAction> newPress, ArrayList<StandardAction> currentPress,
                FunctionConfig matchConfig) {
            if (moveFunction != null) {
                //上次已有移动鼠标的功能
                updateMoveState(newPress, currentPress);
                return;
            }

            //上次没有移动鼠标
            for (StandardAction action : newPress) {
                if (action.standardKey.type == KeyType.BTN) continue;
                //按下方向键或摇杆
                moveFunction = findMouseMoveFunction(action.standardKey, matchConfig);
                if (moveFunction == null) continue;
                updateMoveState(newPress, currentPress);
                break;
            }
        }

        /**
         * 根据当前按键,找到(首个)符合的移动鼠标功能
         *
         * @param standardKey 按键类型是[方向键,摇杆]的
         */
        private FunctionItem findMouseMoveFunction(StandardKey standardKey, FunctionConfig matchConfig) {
            MouseMoveType targetType;
            if (standardKey.type == KeyType.ARROW) {
                targetType = MouseMoveType.ARROW;
            } else if (standardKey.key.contains("L")) {
                targetType = MouseMoveType.STICK_LEFT;
            } else {
                targetType = MouseMoveType.STICK_RIGHT;
            }
            for (FunctionItem function : matchConfig.lstFunction) {
                if (function.functionType != FunctionType.MOUSE_MOVE) continue;
                if (function.mouseMoveType == targetType) return function;
            }
            return null;
        }

        /**
         * .将currentPress转成鼠标移动功能[开始移动,刷新移动速度,停止移动];
         * .移除newPress中的[鼠标移动按键事件]
         */
        private void updateMoveState(ArrayList<StandardAction> newPress, ArrayList<StandardAction> currentPress) {
            boolean hasMoveKey = false;
            float xSpeed = 0, ySpeed = 0;
            if (moveFunction.mouseMoveType == MouseMoveType.ARROW) {
                pressedStick = false;
                //移除newPress中的[鼠标移动按键事件];即使没有移除,也不代表没有鼠标移动功能
                newPress.removeIf(press -> press.standardKey.type == KeyType.ARROW);
                for (StandardAction action : currentPress) {
                    if (action.standardKey.type != KeyType.ARROW) continue;
                    hasMoveKey = true;
                    if (StandardKeys.ARROW_N.key.equals(action.standardKey.key)) {
                        ySpeed = -1;
                    } else if (StandardKeys.ARROW_EN.key.equals(action.standardKey.key)) {
                        ySpeed = -1;
                        xSpeed = 1;
                    } else if (StandardKeys.ARROW_E.key.equals(action.standardKey.key)) {
                        xSpeed = 1;
                    } else if (StandardKeys.ARROW_ES.key.equals(action.standardKey.key)) {
                        ySpeed = 1;
                        xSpeed = 1;
                    } else if (StandardKeys.ARROW_S.key.equals(action.standardKey.key)) {
                        ySpeed = 1;
                    } else if (StandardKeys.ARROW_WS.key.equals(action.standardKey.key)) {
                        ySpeed = 1;
                        xSpeed = -1;
                    } else if (StandardKeys.ARROW_W.key.equals(action.standardKey.key)) {
                        xSpeed = -1;
                    } else if (StandardKeys.ARROW_WN.key.equals(action.standardKey.key)) {
                        ySpeed = -1;
                        xSpeed = -1;
                    }
                    break;
                }
            } else {
                String stickTag = moveFunction.mouseMoveType == MouseMoveType.STICK_LEFT ? "L" : "R";
                String stickButton = moveFunction.mouseMoveType == MouseMoveType.STICK_LEFT ? "L3" : "R3";
                pressedStick = false;
                //移除newPress中的[鼠标移动按键事件];即使没有移除,也不代表没有鼠标移动功能
                newPress.removeIf(press -> press.standardKey.type == KeyType.STICK && press.standardKey.key.contains(stickTag));
                for (StandardAction action : currentPress) {
                    StandardKey standardKey = action.standardKey;
                    if (standardKey.type == KeyType.BTN && standardKey.key.equals(stickButton)) {
                        pressedStick = true;
                        continue;
                    }
                    if (standardKey.type != KeyType.STICK || !standardKey.key.contains(stickTag)) continue;
                    hasMoveKey = true;
                    if (standardKey.key.contains("上")) {
                        ySpeed = -standardKey.range;
                    } else if (standardKey.key.contains("下")) {
                        ySpeed = standardKey.range;
                    } else if (standardKey.key.contains("左")) {
                        xSpeed = -standardKey.range;
                    } else if (standardKey.key.contains("右")) {
                        xSpeed = standardKey.range;
                    }
                    //不能break,因为摇杆可能是两个轴的
                }
            }
            this.xSpeed = xSpeed;
            this.ySpeed = ySpeed;
            if (!hasMoveKey) {
                moveFunction = null;
                return;//不是移动鼠标事件
            }

            //log.info("xSpeed={}, ySpeed={}", xSpeed, ySpeed);
            lock.lock();
            condition.signalAll();//唤醒等待中的[鼠标移动线程],线程也可能没有在等待
            lock.unlock();
        }

    }

    private static class ButtonHandler {
        /**
         * {@link FunctionType#MOUSE_BUTTON}
         */
        private final ArrayList<FunctionItem> lstPressed = new ArrayList<>();
        private final Robot robot;

        private ButtonHandler(Robot robot) {
            this.robot = robot;
        }

        private void releaseAllActiveFunction() {
            for (FunctionItem function : lstPressed) robotRelease(function);
            lstPressed.clear();
        }

        private void handleRelease(StandardEvent e, StandardKey standardKey) {
            IJoyEventHandler.matchReleaseBtnFunction(standardKey, lstPressed, this::robotRelease);
        }

        private void handleNewPress(ArrayList<StandardAction> newPress, ArrayList<StandardAction> currentPress,
                FunctionConfig matchConfig) {
            for (Iterator<StandardAction> iterator = newPress.iterator(); iterator.hasNext(); ) {
                StandardAction press = iterator.next();
                log.debug("press={}", press);

                FunctionItem function = IJoyEventHandler.matchPressBtnFunction(press, currentPress, matchConfig,
                        f -> f.functionType == FunctionType.MOUSE_BUTTON && !f.lstStandardKey.isEmpty());
                if (function == null) continue;
                //有符合功能
                iterator.remove();//移除已处理的[新按下事件]
                if (!lstPressed.contains(function)) {
                    //一个事件中,同时按下多个键时,会有多个符合的action,所以要过滤,但不能跳过上面的匹配逻辑
                    lstPressed.add(function);
                    robotPress(function);
                }
            }
        }

        private void robotRelease(FunctionItem function) {
            //log.info("[{}].release", function.getString(null));
            robot.mouseRelease(function.mouseButton.button);
        }

        private void robotPress(FunctionItem function) {
            //log.info("[{}].press", function.getString(null));
            robot.mousePress(function.mouseButton.button);
        }
    }
}
